#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

const uint32_t gcap = 5;

template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _bq.size() >= _cap;
    }

    bool IsEmpty()
    {
        return _bq.empty();
    }

public:
    BlockQueue(uint32_t cap = gcap) : _cap(cap), _c_cond_num(0), _p_cond_num(0)
    {
        pthread_mutex_init(&_lock, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
    }

    void Push(const T &in)
    {
        pthread_mutex_lock(&_lock);
        //判断使用while而不是if的原因:
        //a.避免调用pthread_cond_wait失败，线程继续往后进行
        //b.避免伪唤醒情况
        while (IsFull())//队列满了
        {
            // 线程在临界区进行等待的原因:
            // 判断队列是否满了本身就是要访问临界资源，所以生产者要申请锁在临界区内判断
            // 而要获得等待结果就必须在临界区内等待

            // 1、线程进行等待的时候，会自动释放锁
            // 2、线程被唤醒后自动去竞争锁，获得锁资源
            _p_cond_num++;
            pthread_cond_wait(&_p_cond, &_lock); // 线程唤醒后会回到这个位置
            _p_cond_num--;
        }
        //队列有空余
        _bq.push(in); // 完成生产
        // 唤醒消费者,这一步在放在解锁前后都可以
        if (_c_cond_num > 0)
            pthread_cond_signal(&_c_cond);
        pthread_mutex_unlock(&_lock);
    }

    void Pop(T *out)
    {
        pthread_mutex_lock(&_lock);

        while (IsEmpty())//队列为空
        {
            _c_cond_num++;
            pthread_cond_wait(&_c_cond, &_lock);
            _c_cond_num--;
        }
        //队列不为空
        *out = _bq.front();
        _bq.pop(); // 完成消费
        // 唤醒生产者,这一步在放在解锁前后都可以
        if (_p_cond_num > 0)
            pthread_cond_signal(&_p_cond); 
        pthread_mutex_unlock(&_lock);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_c_cond);
        pthread_cond_destroy(&_p_cond);
    }

private:
    std::queue<T> _bq;
    uint32_t _cap;          // 队列容量
    pthread_mutex_t _lock;  // 互斥锁
    pthread_cond_t _c_cond; // 消费者条件变量
    pthread_cond_t _p_cond; // 生产者条件变量

    int _c_cond_num; // 正在阻塞的消费者
    int _p_cond_num; // 正在阻塞的生产者
};